@@ -25,17 +25,18 @@ end |
||
25 | 25 |
|
26 | 26 |
# Optional libraries. To conserve RAM, comment out any that you don't need, |
27 | 27 |
# then run `bundle` and commit the updated Gemfile and Gemfile.lock. |
28 |
-gem 'twilio-ruby', '~> 3.11.5' # TwilioAgent |
|
29 |
-gem 'ruby-growl', '~> 4.1.0' # GrowlAgent |
|
30 |
-gem 'net-ftp-list', '~> 3.2.8' # FtpsiteAgent |
|
31 |
-gem 'wunderground', '~> 1.2.0' # WeatherAgent |
|
32 |
-gem 'forecast_io', '~> 2.0.0' # WeatherAgent |
|
33 |
-gem 'rturk', '~> 2.12.1' # HumanTaskAgent |
|
34 |
-gem 'hipchat', '~> 1.2.0' # HipchatAgent |
|
35 |
-gem 'xmpp4r', '~> 0.5.6' # JabberAgent |
|
36 |
-gem 'mqtt' # MQTTAgent |
|
37 |
-gem 'slack-notifier', '~> 1.0.0' # SlackAgent |
|
38 |
-gem 'hypdf', '~> 1.0.7' # PDFInfoAgent |
|
28 |
+gem 'twilio-ruby', '~> 3.11.5' # TwilioAgent |
|
29 |
+gem 'ruby-growl', '~> 4.1.0' # GrowlAgent |
|
30 |
+gem 'net-ftp-list', '~> 3.2.8' # FtpsiteAgent |
|
31 |
+gem 'wunderground', '~> 1.2.0' # WeatherAgent |
|
32 |
+gem 'forecast_io', '~> 2.0.0' # WeatherAgent |
|
33 |
+gem 'rturk', '~> 2.12.1' # HumanTaskAgent |
|
34 |
+gem 'hipchat', '~> 1.2.0' # HipchatAgent |
|
35 |
+gem 'xmpp4r', '~> 0.5.6' # JabberAgent |
|
36 |
+gem 'mqtt' # MQTTAgent |
|
37 |
+gem 'slack-notifier', '~> 1.0.0' # SlackAgent |
|
38 |
+gem 'hypdf', '~> 1.0.7' # PDFInfoAgent |
|
39 |
+gem 'telegram-bot-ruby', '~> 0.4.1' # TelegramAgent |
|
39 | 40 |
|
40 | 41 |
# Weibo Agents |
41 | 42 |
gem 'weibo_2', github: 'cantino/weibo_2', branch: 'master' |
@@ -112,6 +112,10 @@ GEM |
||
112 | 112 |
multi_json (>= 1.0.0) |
113 | 113 |
aws-sdk-core (2.2.15) |
114 | 114 |
jmespath (~> 1.0) |
115 |
+ axiom-types (0.1.1) |
|
116 |
+ descendants_tracker (~> 0.0.4) |
|
117 |
+ ice_nine (~> 0.11.0) |
|
118 |
+ thread_safe (~> 0.3, >= 0.3.1) |
|
115 | 119 |
bcrypt (3.1.10) |
116 | 120 |
better_errors (1.1.0) |
117 | 121 |
coderay (>= 1.0.0) |
@@ -146,6 +150,8 @@ GEM |
||
146 | 150 |
chronic (0.10.2) |
147 | 151 |
cliver (0.3.2) |
148 | 152 |
coderay (1.1.0) |
153 |
+ coercible (1.0.0) |
|
154 |
+ descendants_tracker (~> 0.0.1) |
|
149 | 155 |
coffee-rails (4.1.1) |
150 | 156 |
coffee-script (>= 2.2.0) |
151 | 157 |
railties (>= 4.0.0, < 5.1.x) |
@@ -170,6 +176,8 @@ GEM |
||
170 | 176 |
activesupport (>= 3.0, < 5.0) |
171 | 177 |
delorean (2.1.0) |
172 | 178 |
chronic |
179 |
+ descendants_tracker (0.0.4) |
|
180 |
+ thread_safe (~> 0.3, >= 0.3.1) |
|
173 | 181 |
devise (3.5.4) |
174 | 182 |
bcrypt (~> 3.0) |
175 | 183 |
orm_adapter (~> 0.1) |
@@ -225,6 +233,8 @@ GEM |
||
225 | 233 |
dotenv (>= 0.7) |
226 | 234 |
thor (>= 0.13.6) |
227 | 235 |
formatador (0.2.5) |
236 |
+ gene_pool (1.4.1) |
|
237 |
+ thread_safe |
|
228 | 238 |
geokit (1.8.5) |
229 | 239 |
multi_json (>= 1.3.2) |
230 | 240 |
geokit-rails (2.0.1) |
@@ -281,6 +291,7 @@ GEM |
||
281 | 291 |
hypdf (1.0.7) |
282 | 292 |
httmultiparty (= 0.3.10) |
283 | 293 |
i18n (0.7.0) |
294 |
+ ice_nine (0.11.2) |
|
284 | 295 |
jmespath (1.1.3) |
285 | 296 |
jquery-rails (3.1.3) |
286 | 297 |
railties (>= 3.0, < 5.0) |
@@ -371,6 +382,11 @@ GEM |
||
371 | 382 |
multi_json (~> 1.3) |
372 | 383 |
omniauth-oauth (~> 1.0) |
373 | 384 |
orm_adapter (0.5.0) |
385 |
+ persistent_http (1.0.6) |
|
386 |
+ gene_pool (>= 1.3) |
|
387 |
+ persistent_httparty (0.1.2) |
|
388 |
+ httparty (~> 0.9) |
|
389 |
+ persistent_http (< 2) |
|
374 | 390 |
pg (0.18.3) |
375 | 391 |
poltergeist (1.8.1) |
376 | 392 |
capybara (~> 2.1) |
@@ -515,6 +531,10 @@ GEM |
||
515 | 531 |
net-ssh (>= 2.8.0) |
516 | 532 |
string-scrub (0.0.5) |
517 | 533 |
systemu (2.6.4) |
534 |
+ telegram-bot-ruby (0.4.1) |
|
535 |
+ httmultiparty |
|
536 |
+ persistent_httparty |
|
537 |
+ virtus |
|
518 | 538 |
term-ansicolor (1.3.0) |
519 | 539 |
tins (~> 1.0) |
520 | 540 |
therubyracer (0.12.2) |
@@ -559,6 +579,11 @@ GEM |
||
559 | 579 |
macaddr (~> 1.0) |
560 | 580 |
uuidtools (2.1.5) |
561 | 581 |
vcr (2.9.2) |
582 |
+ virtus (1.0.5) |
|
583 |
+ axiom-types (~> 0.1) |
|
584 |
+ coercible (~> 1.0) |
|
585 |
+ descendants_tracker (~> 0.0, >= 0.0.3) |
|
586 |
+ equalizer (~> 0.0, >= 0.0.9) |
|
562 | 587 |
warden (1.2.4) |
563 | 588 |
rack (>= 1.0) |
564 | 589 |
webmock (1.17.4) |
@@ -664,6 +689,7 @@ DEPENDENCIES |
||
664 | 689 |
spring (~> 1.6.3) |
665 | 690 |
spring-commands-rspec (~> 1.0.4) |
666 | 691 |
string-scrub |
692 |
+ telegram-bot-ruby (~> 0.4.1) |
|
667 | 693 |
therubyracer (~> 0.12.2) |
668 | 694 |
tumblr_client! |
669 | 695 |
twilio-ruby (~> 3.11.5) |
@@ -0,0 +1,92 @@ |
||
1 |
+require 'telegram/bot' |
|
2 |
+require 'open-uri' |
|
3 |
+require 'tempfile' |
|
4 |
+ |
|
5 |
+module Agents |
|
6 |
+ class TelegramAgent < Agent |
|
7 |
+ cannot_be_scheduled! |
|
8 |
+ cannot_create_events! |
|
9 |
+ no_bulk_receive! |
|
10 |
+ |
|
11 |
+ gem_dependency_check { defined?(Telegram) } |
|
12 |
+ |
|
13 |
+ description <<-MD |
|
14 |
+ #{'# Include `telegram-bot-ruby` in your Gemfile to use this Agent!' if dependencies_missing?} |
|
15 |
+ |
|
16 |
+ The Telegram Agent receives and collects events and sends them via [Telegram](https://telegram.org/). |
|
17 |
+ |
|
18 |
+ It is assumed that events have either a `text`, `photo`, `audio`, `document` or `video` key. You can use the EventFormattingAgent if your event does not provide these keys. |
|
19 |
+ |
|
20 |
+ The value of `text` key is sent as a plain text message. |
|
21 |
+ The value of `photo`, `audio`, `document` and `video` keys should be an url which contents are sent to you according to the type. |
|
22 |
+ |
|
23 |
+ **Setup** |
|
24 |
+ |
|
25 |
+ 1. obtain an `auth_token` by [creating a new bot](https://telegram.me/botfather). |
|
26 |
+ 2. [send a private message to your bot](https://telegram.me/YourHuginnBot) |
|
27 |
+ 3. obtain your private `chat_id` [from the recently started conversation](https://api.telegram.org/bot<auth_token>/getUpdates) |
|
28 |
+ MD |
|
29 |
+ |
|
30 |
+ def default_options |
|
31 |
+ { |
|
32 |
+ auth_token: 'xxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', |
|
33 |
+ chat_id: 'xxxxxxxx' |
|
34 |
+ } |
|
35 |
+ end |
|
36 |
+ |
|
37 |
+ def validate_options |
|
38 |
+ errors.add(:base, 'auth_token is required') unless options['auth_token'].present? |
|
39 |
+ errors.add(:base, 'chat_id is required') unless options['chat_id'].present? |
|
40 |
+ end |
|
41 |
+ |
|
42 |
+ def working? |
|
43 |
+ received_event_without_error? && !recent_error_logs? |
|
44 |
+ end |
|
45 |
+ |
|
46 |
+ def receive(incoming_events) |
|
47 |
+ incoming_events.each do |event| |
|
48 |
+ receive_event event |
|
49 |
+ end |
|
50 |
+ end |
|
51 |
+ |
|
52 |
+ private |
|
53 |
+ |
|
54 |
+ TELEGRAM_FIELDS = { |
|
55 |
+ text: :send_message, |
|
56 |
+ photo: :send_photo, |
|
57 |
+ audio: :send_audio, |
|
58 |
+ document: :send_document, |
|
59 |
+ video: :send_video |
|
60 |
+ }.freeze |
|
61 |
+ |
|
62 |
+ def receive_event(event) |
|
63 |
+ TELEGRAM_FIELDS.each do |field, method| |
|
64 |
+ payload = load_field event, field |
|
65 |
+ next unless payload |
|
66 |
+ send_telegram_message method, field => payload |
|
67 |
+ end |
|
68 |
+ end |
|
69 |
+ |
|
70 |
+ def send_telegram_message(method, params) |
|
71 |
+ params[:chat_id] = interpolated['chat_id'] |
|
72 |
+ Telegram::Bot::Client.run interpolated['auth_token'] do |bot| |
|
73 |
+ bot.api.send method, params |
|
74 |
+ end |
|
75 |
+ end |
|
76 |
+ |
|
77 |
+ def load_field(event, field) |
|
78 |
+ payload = event.payload[field] |
|
79 |
+ return false unless payload.present? |
|
80 |
+ return payload if field == :text |
|
81 |
+ load_file payload |
|
82 |
+ end |
|
83 |
+ |
|
84 |
+ def load_file(url) |
|
85 |
+ file = Tempfile.new [File.basename(url), File.extname(url)] |
|
86 |
+ file.binmode |
|
87 |
+ file.write open(url).read |
|
88 |
+ file.rewind |
|
89 |
+ file |
|
90 |
+ end |
|
91 |
+ end |
|
92 |
+end |
@@ -0,0 +1,102 @@ |
||
1 |
+require 'rails_helper' |
|
2 |
+ |
|
3 |
+describe Agents::TelegramAgent do |
|
4 |
+ before do |
|
5 |
+ default_options = { |
|
6 |
+ auth_token: 'xxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', |
|
7 |
+ chat_id: 'xxxxxxxx' |
|
8 |
+ } |
|
9 |
+ @checker = Agents::TelegramAgent.new name: 'Telegram Tester', options: default_options |
|
10 |
+ @checker.user = users(:bob) |
|
11 |
+ @checker.save! |
|
12 |
+ |
|
13 |
+ @sent_messages = [] |
|
14 |
+ stub_methods |
|
15 |
+ end |
|
16 |
+ |
|
17 |
+ def stub_methods |
|
18 |
+ stub.any_instance_of(Agents::TelegramAgent).send_telegram_message do |method, params| |
|
19 |
+ @sent_messages << { method => params } |
|
20 |
+ end |
|
21 |
+ |
|
22 |
+ stub.any_instance_of(Agents::TelegramAgent).load_file do |_url| |
|
23 |
+ :stubbed_file |
|
24 |
+ end |
|
25 |
+ end |
|
26 |
+ |
|
27 |
+ def event_with_payload(payload) |
|
28 |
+ event = Event.new |
|
29 |
+ event.agent = agents(:bob_weather_agent) |
|
30 |
+ event.payload = payload |
|
31 |
+ event.save! |
|
32 |
+ event |
|
33 |
+ end |
|
34 |
+ |
|
35 |
+ describe 'validation' do |
|
36 |
+ before do |
|
37 |
+ expect(@checker).to be_valid |
|
38 |
+ end |
|
39 |
+ |
|
40 |
+ it 'should validate presence of of auth_token' do |
|
41 |
+ @checker.options[:auth_token] = '' |
|
42 |
+ expect(@checker).not_to be_valid |
|
43 |
+ end |
|
44 |
+ |
|
45 |
+ it 'should validate presence of of chat_id' do |
|
46 |
+ @checker.options[:chat_id] = '' |
|
47 |
+ expect(@checker).not_to be_valid |
|
48 |
+ end |
|
49 |
+ end |
|
50 |
+ |
|
51 |
+ describe '#receive' do |
|
52 |
+ it 'processes multiple events properly' do |
|
53 |
+ event_0 = event_with_payload text: 'Looks like its going to rain' |
|
54 |
+ event_1 = event_with_payload text: 'Another text message' |
|
55 |
+ @checker.receive [event_0, event_1] |
|
56 |
+ |
|
57 |
+ expect(@sent_messages).to eq([ |
|
58 |
+ { send_message: { text: 'Looks like its going to rain' } }, |
|
59 |
+ { send_message: { text: 'Another text message' } } |
|
60 |
+ ]) |
|
61 |
+ end |
|
62 |
+ |
|
63 |
+ it 'accepts photo key and uses :send_photo to send the file' do |
|
64 |
+ event = event_with_payload photo: 'https://example.com/image.png' |
|
65 |
+ @checker.receive [event] |
|
66 |
+ |
|
67 |
+ expect(@sent_messages).to eq([{ send_photo: { photo: :stubbed_file } }]) |
|
68 |
+ end |
|
69 |
+ |
|
70 |
+ it 'accepts audio key and uses :send_audio to send the file' do |
|
71 |
+ event = event_with_payload audio: 'https://example.com/sound.mp3' |
|
72 |
+ @checker.receive [event] |
|
73 |
+ |
|
74 |
+ expect(@sent_messages).to eq([{ send_audio: { audio: :stubbed_file } }]) |
|
75 |
+ end |
|
76 |
+ |
|
77 |
+ it 'accepts document key and uses :send_document to send the file' do |
|
78 |
+ event = event_with_payload document: 'https://example.com/document.pdf' |
|
79 |
+ @checker.receive [event] |
|
80 |
+ |
|
81 |
+ expect(@sent_messages).to eq([{ send_document: { document: :stubbed_file } }]) |
|
82 |
+ end |
|
83 |
+ |
|
84 |
+ it 'accepts video key and uses :send_video to send the file' do |
|
85 |
+ event = event_with_payload video: 'https://example.com/video.avi' |
|
86 |
+ @checker.receive [event] |
|
87 |
+ |
|
88 |
+ expect(@sent_messages).to eq([{ send_video: { video: :stubbed_file } }]) |
|
89 |
+ end |
|
90 |
+ end |
|
91 |
+ |
|
92 |
+ describe '#working?' do |
|
93 |
+ it 'is not working without having received an event' do |
|
94 |
+ expect(@checker).not_to be_working |
|
95 |
+ end |
|
96 |
+ |
|
97 |
+ it 'is working after receiving an event without error' do |
|
98 |
+ @checker.last_receive_at = Time.now |
|
99 |
+ expect(@checker).to be_working |
|
100 |
+ end |
|
101 |
+ end |
|
102 |
+end |